#include "logger.h"
#include "socket.h"

void set_linger(int fd)
{
   static struct linger ln;
   ln.l_onoff = 1; ln.l_linger = 0;
   static const int len = sizeof(ln);

   setsockopt(fd, SOL_SOCKET, SO_LINGER,(const char*)&ln, len);
}

void set_buf_size(int fd)
{
   static const int buf_size = 10 * 1024;
   static const int len = sizeof(buf_size);

   setsockopt(fd, SOL_SOCKET, SO_SNDBUF,(const char*)&buf_size, len);
   setsockopt(fd, SOL_SOCKET, SO_RCVBUF,(const char*)&buf_size, len);
}

void set_reuse_addr(int fd)
{
   static const int flag = 1;
   static const int len = sizeof(flag);

   setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&flag, len);
}

void set_keep_alive(int fd)
{
    static const int flag = 1;
    static const int len = sizeof(flag);

    setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&flag, len);
}

#ifdef WIN32

void set_blocking(int fd, bool isblock)
{
    unsigned long x = 1; if(isblock) x = 0;

    ioctlsocket(fd, FIONBIO, &x);
}

void set_time_out(int fd, int timeout)
{
    timeout *= 1000; //30s

    setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout));
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
}

#else

void set_blocking(int fd, bool isblock)
{
    int opts = fcntl(fd, F_GETFL); 

    if(isblock) opts &= ~O_NONBLOCK; else opts |= O_NONBLOCK;

    fcntl(fd, F_SETFL, opts);
}

void set_time_out(int fd, int timeout)
{
    struct timeval flag = { timeout, 0 };//30s

    setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &flag, sizeof(flag));
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &flag, sizeof(flag));
}

#endif

string en_addr_info(int fd, const char* ip, ushort port)
{
    char curtime[20] = { 0 }; char addrinfo[64] = { 0 };

    sprintf(addrinfo, "%d_%s:%u_%s", fd, ip, port, get_cur_time(curtime, 20));

    return addrinfo;
}

bool de_addr_info(const char* addrinfo, int& fd, char* ip, ushort& port)
{
    if(0 == strlen(addrinfo)) return false;

    fd = atoi(addrinfo);
 
    const char* sub1 = strchr(addrinfo, '_') + 1;
 
    const char* sub2 = strchr(addrinfo, ':') + 1;

    if(NULL == sub1 || NULL == sub2) return false;
    
    strncpy(ip, sub1, sub2-sub1-1); ip[sub2-sub1-1] = '\0';

    port = atoi(sub2);

    return true;
}

CStartSocket* g_startSocket = &sStartSocket;

CStartSocket::CStartSocket()
{
#ifdef WIN32
    WSAData    wsaData;

    if(NO_ERROR != WSAStartup(MAKEWORD(2,2), &wsaData))
    {
        exe_exit("WSAStartup failed");
    }
#endif
}

CStartSocket::~CStartSocket()
{
#ifdef WIN32
    WSACleanup();
#endif
}

SocketInfo::SocketInfo()
: _fd(0)
{
}

SocketInfo::~SocketInfo()
{
    if(_fd > 0)
    {
        SOCKET_CLOSE(_fd);
    }
}

CSocket::CSocket()
: m_timeout(30)
, m_isblock(false)
, m_callbackfunc(NULL)
{
    m_log = GET_LOG("_socket");
}

CSocket::~CSocket()
{
}

bool CSocket::ConnectToServer()
{
    m_socketinfo._fd = socket(AF_INET, SOCK_STREAM, 0); 

    if(m_socketinfo._fd <= 0)
    {
        LOG_ERROR(m_log, "[%s:%d], socket failed, [err:%d]\n", m_socketinfo._ip, m_socketinfo._port, SOCKET_ERRNO);

        return false;
    }

    set_linger(m_socketinfo._fd);
    set_buf_size(m_socketinfo._fd);
    set_time_out(m_socketinfo._fd, m_timeout);

    struct sockaddr_in addr_in;
    addr_in.sin_family = AF_INET;
    addr_in.sin_port = htons(m_socketinfo._port);
    addr_in.sin_addr.s_addr = inet_addr(m_socketinfo._ip);

    if(connect(m_socketinfo._fd, (struct sockaddr*)&addr_in, sizeof(sockaddr_in)) < 0)
    {
        LOG_ERROR(m_log, "[%s:%d], connect failed [err:%d]\n", m_socketinfo._ip, m_socketinfo._port, SOCKET_ERRNO);
        
        return false;
    }

    set_blocking(m_socketinfo._fd, m_isblock);

    strcpy(m_socketinfo._addrinfo, en_addr_info(m_socketinfo._fd, m_socketinfo._ip, m_socketinfo._port).c_str());

    return true;
}

void CSocket::SetBlock(bool isblock)
{
    m_isblock = isblock;
}

void CSocket::SetTimeOut(int timeout)
{
    m_timeout = timeout;
}

void CSocket::SetCallbackFunc(socket_callback_func func)
{
    m_callbackfunc = func;
}

int CSocket::Recv(const char* addrinfo, int fd, char* outbuf, int outlen)
{
    int result = recv(fd, outbuf, outlen, 0);

    if(result > 0)
    {
        LOG_INFO(m_log, "[recvmsg:%s]\n[%d,%s]\n", addrinfo, result, outbuf);
    }

    return result;
}

int CSocket::Send(const char* addrinfo, int fd, const char* inbuf, int inlen)
{
    int size = 0;

    while(size < inlen)
    {
        int temp = send(fd, inbuf+size, inlen-size, 0);

        if(temp <= 0)
        {
            LOG_ERROR(m_log, "[sendmsg:%s failed] [err:%d]\n[%d,%s]\n", addrinfo, SOCKET_ERRNO, inlen, inbuf);

            return temp;
        }
        
        size += temp;
    }

    LOG_INFO(m_log, "[sendmsg:%s success]\n[%d,%s] \n", addrinfo, inlen, inbuf);

    return inlen;
}
